package ltd.ueic.allypay.alipay.service;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.*;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import lombok.extern.slf4j.Slf4j;
import ltd.ueic.allypay.alipay.config.AlipayConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @author Kevin Yang
 * @version 1.0.0
 * Created by Kevin Yang on 2018/11/30 15:44.
 */
@Slf4j
@Service
public class AliPayService {

    @Autowired
    private AlipayConfig alipayConfig;

    /**
     * WAP支付调用接口 | 外部商户创建订单并支付
     * 必选参数 -> subject 商品的标题/交易标题/订单标题/订单关键字等。
     * 必选参数 -> out_trade_no 商户网站唯一订单号
     * 必选参数 -> product_code 销售产品码，商家和支付宝签约的产品码  (此处固定值：QUICK_WAP_WAY)
     * 必选参数 -> total_amount 订单总金额，单位为元，精确到小数点后两位，取值范围[0.01,100000000]
     *
     * @param bizModel
     * @return
     */
    public AlipayTradeWapPayResponse wapPay(AlipayTradeWapPayModel bizModel, String returnUrl, String notifyUrl) throws IllegalArgumentException {
        if (StringUtils.isEmpty(bizModel.getOutTradeNo())
                || StringUtils.isEmpty(bizModel.getTotalAmount())
                || StringUtils.isEmpty(bizModel.getSubject())) {
            throw new IllegalArgumentException("必要参数[subject | out_trade_no | total_amount]不能为空");
        }
        bizModel.setProductCode("QUICK_WAP_WAY");
        // SDK 公共请求类，包含公共请求参数，以及封装了签名与验签，开发者无需关注签名与验签
        // 调用RSA签名方式
        AlipayClient client = alipayConfig.getAliPayClient();
        AlipayTradeWapPayRequest alipayRequest = new AlipayTradeWapPayRequest();

        alipayRequest.setBizModel(bizModel);
        // 设置异步通知地址
        alipayRequest.setNotifyUrl(StringUtils.isEmpty(notifyUrl) ? alipayConfig.getNotifyUrl() : notifyUrl);
        // 设置同步地址
        alipayRequest.setReturnUrl(StringUtils.isEmpty(returnUrl) ? alipayConfig.getReturnUrl() : returnUrl);

        // form表单生产
        try {
            // 调用SDK生成表单
            AlipayTradeWapPayResponse response = client.pageExecute(alipayRequest);
            return response;
        } catch (AlipayApiException e) {
            log.error("AliPayService--> wapPay | 调用WAP支付SDK出现异常", e);
        }
        return null;
    }

    /**
     * 电脑网页支付调用接口 | PC场景下单并支付
     * 必选参数 -> subject 商品的标题/交易标题/订单标题/订单关键字等。
     * 必选参数 -> out_trade_no 商户网站唯一订单号
     * 必选参数 -> product_code 销售产品码，商家和支付宝签约的产品码  (此处固定值：FAST_INSTANT_TRADE_PAY)
     * 必选参数 -> total_amount 订单总金额，单位为元，精确到小数点后两位，取值范围[0.01,100000000]
     *
     * @param bizModel
     * @return
     */
    public AlipayTradePagePayResponse pagePay(AlipayTradePagePayModel bizModel, String returnUrl, String notifyUrl) throws IllegalArgumentException {
        if (StringUtils.isEmpty(bizModel.getOutTradeNo())
                || StringUtils.isEmpty(bizModel.getTotalAmount())
                || StringUtils.isEmpty(bizModel.getSubject())) {
            throw new IllegalArgumentException("必要参数[subject | out_trade_no | total_amount]不能为空");
        }
        bizModel.setProductCode("FAST_INSTANT_TRADE_PAY");
        // SDK 公共请求类，包含公共请求参数，以及封装了签名与验签，开发者无需关注签名与验签
        // 调用RSA签名方式
        AlipayClient client = alipayConfig.getAliPayClient();
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();

        alipayRequest.setBizModel(bizModel);
        // 设置异步通知地址
        alipayRequest.setNotifyUrl(StringUtils.isEmpty(notifyUrl) ? alipayConfig.getNotifyUrl() : notifyUrl);
        // 设置同步地址
        alipayRequest.setReturnUrl(StringUtils.isEmpty(returnUrl) ? alipayConfig.getReturnUrl() : returnUrl);

        // form表单生产
        try {
            // 调用SDK生成表单
            AlipayTradePagePayResponse response = client.pageExecute(alipayRequest);
            return response;
        } catch (AlipayApiException e) {
            log.error("AliPayService--> pagePay | 调用PC支付SDK出现异常", e);
        }
        return null;
    }

    /**
     * 用于交易创建后，用户在一定时间内未进行支付，可调用该接口直接将未付款的交易进行关闭。
     * 商户订单号和支付宝交易号不能同时为空。 trade_no、  out_trade_no如果同时存在优先取trade_no
     *
     * @param outTradeNo 商户订单号 | 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。
     * @param tradeNo    支付宝交易号 | 该交易在支付宝系统中的交易流水号。最短 16 位，最长 64 位。
     * @param operatorId 卖家端自定义的的操作员 ID (可选参数)
     * @return
     */
    public AlipayTradeCloseResponse close(String outTradeNo, String tradeNo, String operatorId) {
        /**********************/
        // SDK 公共请求类，包含公共请求参数，以及封装了签名与验签，开发者无需关注签名与验签
        AlipayClient client = alipayConfig.getAliPayClient();
        AlipayTradeCloseRequest alipay_request = new AlipayTradeCloseRequest();

        AlipayTradeCloseModel model = new AlipayTradeCloseModel();
        model.setOutTradeNo(outTradeNo);
        model.setTradeNo(tradeNo);
        model.setOperatorId(operatorId);
        alipay_request.setBizModel(model);

        try {
            AlipayTradeCloseResponse alipay_response = client.execute(alipay_request);
            return alipay_response;
        } catch (AlipayApiException e) {
            log.error("AliPayService-->close | 调用关闭交易SDK出现异常", e);
        }
        return null;
    }

    /**
     * 统一收单线下交易查询
     * 该接口提供所有支付宝支付订单的查询，商户可以通过该接口主动查询订单状态，完成下一步的业务逻辑。 需要调用查询接口的情况： 当商户后台、网络、服务器等出现异常，商户系统最终未接收到支付通知； 调用支付接口后，返回系统错误或未知交易状态情况； 调用alipay.trade.pay，返回INPROCESS的状态； 调用alipay.trade.cancel之前，需确认支付状态；
     * trade_no,out_trade_no如果同时存在优先取trade_no
     *
     * @param outTradeNo 订单支付时传入的商户订单号,和支付宝交易号不能同时为空。
     * @param tradeNo    支付宝交易号，和商户订单号不能同时为空
     * @param orgPid     银行间联模式下有用，其它场景请不要使用；双联通过该参数指定需要查询的交易所属收单机构的pid;
     * @return
     */
    public AlipayTradeQueryResponse query(String outTradeNo, String tradeNo, String orgPid) {
        /**********************/
        // SDK 公共请求类，包含公共请求参数，以及封装了签名与验签，开发者无需关注签名与验签
        AlipayClient client = alipayConfig.getAliPayClient();
        AlipayTradeQueryRequest alipay_request = new AlipayTradeQueryRequest();

        AlipayTradeQueryModel model = new AlipayTradeQueryModel();
        model.setOutTradeNo(outTradeNo);
        model.setTradeNo(tradeNo);
        model.setOrgPid(orgPid);
        alipay_request.setBizModel(model);

        try {
            AlipayTradeQueryResponse alipay_response = client.execute(alipay_request);
            return alipay_response;
        } catch (AlipayApiException e) {
            log.error("AliPayService-->query | 调用订单查询接口SDK出现异常", e);
        }
        return null;
    }

    /**
     * 统一收单交易退款接口
     * 商户订单号和支付宝交易号不能同时为空。 trade_no、  out_trade_no如果同时存在优先取trade_no
     *
     * @param outTradeNo   商户订单号，和支付宝交易号二选一
     * @param tradeNo      支付宝交易号，和商户订单号二选一
     * @param refundAmount 退款金额，不能大于订单总金额
     * @param refundReason 退款的原因说明
     * @param outRequestNo 标识一次退款请求，同一笔交易多次退款需要保证唯一，如需部分退款，则此参数必传。
     * @return
     */
    public AlipayTradeRefundResponse refund(String outTradeNo, String tradeNo, String refundAmount, String refundReason, String outRequestNo) {
        /**********************/
        // SDK 公共请求类，包含公共请求参数，以及封装了签名与验签，开发者无需关注签名与验签
        AlipayClient client = alipayConfig.getAliPayClient();
        AlipayTradeRefundRequest alipay_request = new AlipayTradeRefundRequest();

        AlipayTradeRefundModel model = new AlipayTradeRefundModel();
        model.setOutTradeNo(outTradeNo);
        model.setTradeNo(tradeNo);
        model.setRefundAmount(refundAmount);
        model.setRefundReason(refundReason);
        model.setOutRequestNo(outRequestNo);
        alipay_request.setBizModel(model);

        try {
            AlipayTradeRefundResponse alipay_response = client.execute(alipay_request);
            return alipay_response;
        } catch (AlipayApiException e) {
            log.error("AliPayService-->refund | 调用统一收单交易退款接口SDK出现异常", e);
        }
        return null;
    }

    /**
     * 统一收单交易退款查询
     * 商户订单号和支付宝交易号不能同时为空。 trade_no、  out_trade_no如果同时存在优先取trade_no
     *
     * @param outTradeNo   商户订单号，和支付宝交易号二选一
     * @param tradeNo      支付宝交易号，和商户订单号二选一
     * @param outRequestNo 请求退款接口时，传入的退款请求号，如果在退款请求时未传入，则该值为创建交易时的外部交易号
     * @return
     */
    public AlipayTradeFastpayRefundQueryResponse refundQuery(String outTradeNo, String tradeNo, String outRequestNo) {
        /**********************/
        // SDK 公共请求类，包含公共请求参数，以及封装了签名与验签，开发者无需关注签名与验签
        AlipayClient client = alipayConfig.getAliPayClient();
        AlipayTradeFastpayRefundQueryRequest alipay_request = new AlipayTradeFastpayRefundQueryRequest();

        AlipayTradeFastpayRefundQueryModel model = new AlipayTradeFastpayRefundQueryModel();
        model.setOutTradeNo(outTradeNo);
        model.setTradeNo(tradeNo);
        model.setOutRequestNo(outRequestNo);
        alipay_request.setBizModel(model);

        try {
            AlipayTradeFastpayRefundQueryResponse alipay_response = client.execute(alipay_request);
            return alipay_response;
        } catch (AlipayApiException e) {
            log.error("AliPayService-->refundQuery | 调用统一收单交易退款查询SDK出现异常", e);
        }
        return null;
    }

    /**
     * 查询对账单下载地址
     * 账单类型，商户通过接口或商户经开放平台授权后其所属服务商通过接口可以获取以下账单类型：trade、signcustomer；
     *
     * @param billType trade指商户基于支付宝交易收单的业务账单；signcustomer是指基于商户支付宝余额收入及支出等资金变动的帐务账单；
     * @param billDate 账单时间：日账单格式为yyyy-MM-dd，月账单格式为yyyy-MM。
     * @return
     */
    public AlipayDataDataserviceBillDownloadurlQueryResponse downloadUrl(String billType, String billDate) {
        /**********************/
        // SDK 公共请求类，包含公共请求参数，以及封装了签名与验签，开发者无需关注签名与验签
        AlipayClient client = alipayConfig.getAliPayClient();
        AlipayDataDataserviceBillDownloadurlQueryRequest alipay_request = new AlipayDataDataserviceBillDownloadurlQueryRequest();

        AlipayDataDataserviceBillDownloadurlQueryModel model = new AlipayDataDataserviceBillDownloadurlQueryModel();
        model.setBillType(billType);
        model.setBillDate(billDate);
        alipay_request.setBizModel(model);

        try {
            AlipayDataDataserviceBillDownloadurlQueryResponse alipay_response = client.execute(alipay_request);
            return alipay_response;
        } catch (AlipayApiException e) {
            log.error("AliPayService-->downloadUrl | 调用查询对账单下载地址SDK出现异常", e);
        }
        return null;
    }

    /**
     * 验证返回参数的正确性
     *
     * @param params
     * @return
     */
    public Boolean verifyReturn(Map<String, String> params) {

        boolean verify_result = false;
        try {
            //获取支付宝的通知返回参数，可参考技术文档中页面跳转同步通知参数列表(以上仅供参考)//
            //计算得出通知验证结果
            //boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
            verify_result = AlipaySignature.rsaCheckV1(params, alipayConfig.getAlipayPublicKey(), alipayConfig.getCharset(), alipayConfig.getSignType());
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return verify_result;
    }

    /**
     * 获取支付宝GET过来反馈信息
     *
     * @param requestParams
     * @return
     * @throws UnsupportedEncodingException
     */
    public Map<String, String> getPayReturnParams(Map requestParams) throws UnsupportedEncodingException {
        Map<String, String> params = new HashMap<>();
        for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            //乱码解决，这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
            valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }
        return params;
    }
}
